import Redis from 'ioredis';
import log from '../utils/logger';

class RedisClient {
  private static instance: RedisClient;
  private client: Redis;

  private constructor() {
    this.client = new Redis({
      host: process.env.REDIS_HOST || 'localhost',
      port: parseInt(process.env.REDIS_PORT || '6379', 10),
      password: process.env.REDIS_PASSWORD,
      retryStrategy: (times: number) => {
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
      maxRetriesPerRequest: 3
    });

    this.setupEventHandlers();
  }

  private setupEventHandlers() {
    this.client.on('error', (err: Error) => {
      log.error('Redis Client Error:', { message: err.message });
    });

    this.client.on('connect', () => {
      log.info('Redis Client Connected');
    });

    this.client.on('close', () => {
      log.info('Redis Client Connection Closed');
    });

    this.client.on('reconnecting', () => {
      log.info('Redis Client Reconnecting');
    });
  }

  public static getInstance(): RedisClient {
    if (!RedisClient.instance) {
      RedisClient.instance = new RedisClient();
    }
    return RedisClient.instance;
  }

  public async get(key: string): Promise<string | null> {
    try {
      return await this.client.get(key);
    } catch (error) {
      log.error('Redis get error:', { error, key });
      return null;
    }
  }

  public async set(key: string, value: string, expireSeconds?: number): Promise<void> {
    try {
      const expiryKey = `${key}:expiry`;
      if (expireSeconds) {
        const expireAt = Math.floor(Date.now() / 1000) + expireSeconds;
        await this.client.set(key, value);
        await this.client.set(expiryKey, expireAt.toString());
      } else {
        await this.client.set(key, value);
        await this.client.del(expiryKey);
      }
    } catch (error) {
      log.error('Redis set error:', { error, key });
    }
  }

  public async del(key: string): Promise<void> {
    try {
      const expiryKey = `${key}:expiry`;
      await this.client.del(key);
      await this.client.del(expiryKey);
    } catch (error) {
      log.error('Redis del error:', { error, key });
    }
  }

  public async incr(key: string): Promise<number> {
    try {
      const value = await this.client.get(key);
      const newValue = parseInt(value || '0') + 1;
      await this.client.set(key, newValue.toString());
      return newValue;
    } catch (error) {
      log.error('Redis incr error:', { error, key });
      return 0;
    }
  }

  public async expire(key: string, seconds: number): Promise<void> {
    try {
      const expiryKey = `${key}:expiry`;
      const expireAt = Math.floor(Date.now() / 1000) + seconds;
      await this.client.set(expiryKey, expireAt.toString());
    } catch (error) {
      log.error('Redis expire error:', { error, key });
    }
  }

  public async ttl(key: string): Promise<number> {
    try {
      const expiryKey = `${key}:expiry`;
      const expireAtStr = await this.client.get(expiryKey);
      if (!expireAtStr) {
        return 0;
      }

      const expireAt = parseInt(expireAtStr, 10);
      const now = Math.floor(Date.now() / 1000);
      return Math.max(0, expireAt - now);
    } catch (error) {
      log.error('Redis ttl error:', { error, key });
      return 0;
    }
  }

  public async quit(): Promise<void> {
    try {
      await this.client.quit();
      log.info('Redis Client Disconnected');
    } catch (error) {
      log.error('Redis quit error:', { error });
    }
  }
}

export const redisClient = RedisClient.getInstance();
export default redisClient;
